import os
import re
import uuid
import shutil
import commands
import signal
import socket
import ConfigParser

from ivic.core import Config
from ivic.core import Logging
from ivic.core import Utils
from ivic.core.Utils import close_all

logger = Logging.get_logger('ivic.vswitch.Server')

def internalErrorHandler(fun):
    def __wrapper(*args, **kwargs):
        try:
            return fun(*args, **kwargs)
        except Exception, e:
            logger.error('internal error: %s' % str(e))
            logger.exception(e)
            return 1
    return __wrapper

class CheckError(Exception):
    pass

class Checker:

    def __init__(self, config):
        self.config = config

    def do_check(self):
        RWX_OK = os.R_OK | os.W_OK | os.X_OK
        self.__check_dir(self.config['run_dir'], RWX_OK)
        self.__check_dir(self.config['data_dir'], RWX_OK)
        self.__check_dir(self.config['pid_dir'], RWX_OK)
        self.__check_dir(self.config['openvpn']['server_dir'], RWX_OK)
        self.__check_dir(self.config['openvpn']['client_dir'], RWX_OK)
        
        RX_OK = os.R_OK | os.X_OK
        oconf_dir = self.config['openvpn']['templates_dir']
        self.__check_dir(oconf_dir, RX_OK)

        for templ_file in self.config['openvpn']['server_templates']:
           self.__check_file(templ_file, os.R_OK)

        for templ_file in self.config['openvpn']['client_templates']:
           self.__check_file(templ_file, os.R_OK)

        db_file = self.config['db_file'] 
        if not os.path.exists(db_file):
            tmpf = file(db_file, 'w')
            del tmpf
        else:
            self.__check_file(db_file, os.R_OK)
        
    def __check_file(self, file, access_right):
        logger.debug('__check_file: %s' % file)
        if not os.path.exists(file):
            raise CheckError, '__check_file: %s not exist' % file
        if not os.path.isfile(file):
            raise CheckError, '__check_file: %s not a directory' % file
        if not os.access(file, access_right):
            raise CheckError, 'check_dir: no permission to access %s' % file
        
    def __check_dir(self, dir, access_right):   
        logger.debug('check_dir: %s' % dir)
        if not os.path.exists(dir):
            raise CheckError, 'check_dir: %s not exist' % dir
        if not os.path.isdir(dir):
            raise CheckError, 'check_dir: %s not a directory' % dir
        if not os.access(dir, access_right):
            raise CheckError, 'check_dir: no permission to access %s' % dir

def get_port(sid):
    start_port = 16384    
    return start_port + uuid.UUID(sid).int % 19997

def dir_exists(dir1, dir2):
    return os.path.exists(dir1) or os.path.exists(dir2)

def create_dir(dir1, dir2):
    logger.info("create_dir: %s", dir1)
    os.mkdir(dir1)
    logger.info("create_dir: %s", dir2)
    os.mkdir(dir2)
       
def copy_files(dir, files):
    for file in files:
        logger.info("copy_files: copying %s to %s", file, dir)
        shutil.copy(file, dir)

def generate_conf(conf_fn, dict):
    logger.info('generate_conf: %s', conf_fn)
    conf_fd = file(conf_fn, 'r')
    conf = conf_fd.read()
    conf_fd.close()
    conf = conf % dict
    conf_fd = file(conf_fn, 'w')
    conf_fd.write(conf)
    conf_fd.close()

def generate_saconf(conf_fn, cert_fn, key_fn, ca_fn, dict):
    dict['cert'] = file(cert_fn).read()
    dict['key'] = file(key_fn).read()
    dict['ca'] = file(ca_fn).read()
    generate_conf(conf_fn, dict)

def generate_sconf(conf_fn, cert_fn, key_fn, ca_fn, dict):
    dict['cert'] = cert_fn
    dict['key'] = key_fn
    dict['ca'] = ca_fn
    generate_conf(conf_fn, dict)

class Database:

    def __init__(self, config):
        self.db_file = config['db_file']
        self.pid_dir = config['pid_dir']
        
    def register(self, sid, stype):
        logger.info('register_in_db: uuid=%s, type=%s', sid, stype)
        db = file(self.db_file, 'a')
        print >> db, sid, stype
        db.close()

    def deregister(self, sid):
        logger.info('deregister_in_db: uuid=%s', sid)
        db = file(self.db_file, 'r')
        lines = db.readlines()
        db.close()    

        db = file(self.db_file, 'w')
        for l in lines:
            uuid, type = l.split()
            if sid == uuid:
                continue
            print >> db, uuid, type
        db.close()    
        
    def vswitch_type(self, sid):
        db = file(self.db_file, 'r')
        lines = db.readlines()
        db.close()    
        for l in lines:
            uuid, type = l.split()
            if sid == uuid:
                return type
        logger.error('cannot find %s in db', sid)
        return 'unknown' #FIXME should return exception

    def get_pid_file(self, sid):
        return os.path.join(self.pid_dir, sid + '.pid')

    def get_pid(self, sid):
        try:
            pf = file(self.get_pid_file(sid))
            pid = int(pf.read())
            pf.close()
        except:
            pid = 0
        return pid

    def vswitch_started(self, sid):
        try:
            Utils.check_pid_file(self.get_pid_file(sid))
            return False
        except Utils.PidExistsError:
            return True

class VSwitchError(Exception):
    pass

class ServerClass:

    def __init__(self, external_address, config):
        # save param
        self.external_address = external_address
        self.config = config

        # dump config string
        for k, v in self.config.items():
            if type(v) == str:
                logger.debug('config var: %s = %s', k, v)

        for k, v in self.config['openvpn'].items():
            if type(v) == str:
                logger.debug('config[\'openvpn\'] var: %s = %s', k, v)

        # check dirs
        try:
            Checker(self.config).do_check()
        except CheckError, e:
            logger.fatal('unable to initialize vswitch module: %s', str(e))
            raise VSwitchError, 'unable to initalize: %s' % str(e)

        # set database
        self.db = Database(self.config)

    @internalErrorHandler
    def registerVSwitch(self, sid, stype, username = "", ip = "", netmask = ""):
        logger.debug("registerVSwitch: uuid=%s, type=%s, username=%s", sid, stype, username)
        if ip and netmask:
            network = Utils.calculateNetwork(ip, netmask)
            client_address = Utils.generateAddress(network, 10)
        else:
            netmask = '255.255.255.0'
            client_address = '192.168.103.10'

        if stype == 'openvpn':
            tconfig = self.config['openvpn']
            sconf_dir = os.path.join(tconfig['server_dir'], sid)
            cconf_dir = os.path.join(tconfig['client_dir'], sid)
            client_dir = tconfig['client_dir']
            cconf_standalone = os.path.join(client_dir, sid + '.ovpn')
        
            if dir_exists(sconf_dir, cconf_dir):
                logger.warning("registerVSwitch: switch %s already exists", sid)
                return -35#TODO should throw exception or return error state
            else:
                #FIXME possible race condition
                create_dir(sconf_dir, cconf_dir)
                copy_files(sconf_dir, tconfig['server_templates'])
                copy_files(cconf_dir, tconfig['client_templates'])
                shutil.copy(tconfig['client_saconf_template'], cconf_standalone)
                   
	    client_configration = os.path.join(sconf_dir, 'client-confs')
	    os.mkdir(client_configration)
	    default_file = os.path.join(client_configration, 'DEFAULT')
	    file = open(default_file, 'w')
	    file.write('disable')
	    file.close()
	    ivic_file = os.path.join(client_configration, self.config['user'])
            file = open(ivic_file, 'w')
 	    file.close()

	    if username != '':
	    	username_file = os.path.join(client_configration, username)
	    	file = open(username_file, 'w')
	    	#file.write('ifconfig-push ' + ip + ' ' + netmask)
	    	file.close()
            
	    port = get_port(sid)
	    dict = {
                'port': port,
                'netmask': netmask,
                'address': self.external_address,
                'netmask': netmask,
                'client_address': client_address
            }

            logger.debug("registerVSwitch: port=%d", dict['port'])
            sconf_file = os.path.join(sconf_dir, 'server.conf')
            cconf_file = os.path.join(cconf_dir, 'client.conf')
            generate_conf(cconf_file, dict)
	    
            sconfig = Config.load('/etc/ivic/monitor.yaml')
            generate_saconf(cconf_standalone, sconfig['client_key'], sconfig['client_key'], sconfig['server_cert'], dict)
            generate_sconf(sconf_file, sconfig['client_key'], sconfig['client_key'], sconfig['server_cert'], dict)
            
	    self.db.register(sid, stype)
            return port

        else:
	    # if stype == 'ivic-vpn':
	    tconfig = self.config['ivic-vpn']
	    ivicvpn_dir = tconfig['config_dir']
	    if os.path.exists(ivicvpn_dir):
		pass
	    else:
		logger.info("create_dir: %s", ivicvpn_dir)
	 	os.mkdir(ivicvpn_dir)	
            ivicconf_dir = os.path.join(tconfig['config_dir'], sid)
            if os.path.exists(ivicconf_dir):
                logger.warning("registerVSwitch: switch %s already exists", sid)
                return #TODO should throw exception or return error state
            else:
	    	logger.info("create_dir: %s", ivicvpn_dir)
	 	os.mkdir(ivicconf_dir)
            shutil.copy(tconfig['config_templates']+'/client.conf',ivicconf_dir)
	    shutil.copy(tconfig['config_templates']+'/client.ivic',ivicconf_dir)
            dict = {'s_port' : 5100,'c_port': get_port(sid),'vswitch' : sid,'server' : self.external_address, 'client_add' : client_address, 'client_mask' : netmask}
            logger.debug("the c_port is %s",str(get_port(sid)))
	    conf_file1 = os.path.join(ivicconf_dir, 'client.conf')
            generate_conf(conf_file1, dict)
            conf_file2 = os.path.join(ivicconf_dir, 'client.ivic')
	    generate_conf(conf_file2, dict)
            logger.debug("registerVSwitch: port=%d", dict['c_port'])
            self.db.register(sid, stype)
 
    @internalErrorHandler
    def startVSwitch(self, sid):
        stype = self.db.vswitch_type(sid)
        logger.debug("startVSwitch: uuid=%s, type=%s", sid, stype)

        if stype == 'openvpn':
            if self.db.vswitch_started(sid):
                logger.warning('startVSwitch: switch %s already started, do nothing', sid)
                return

            tconfig = self.config['openvpn']
        
            workdir = os.path.join(tconfig['server_dir'], sid)
            pid_file = self.db.get_pid_file(sid)        
            cmd = tconfig['cmd']
            logger.debug('startVSwtich: run daemon %s', ' '.join(cmd))
        
            try:
                logger.debug('run subprocess in dir: %s' % workdir)
                Utils.run_daemon(cmd, workdir, pid_file)
            except Exception, e:
                logger.error('startVSwitch: exception in run_daemon %s', e.message)
                #FIXME should return error state
        else:
	    logger.debug("type is ivic-vpn")
	    tconfig = self.config['ivic-vpn']
	    ivicvpn_program = tconfig['ivicserver_program']
            pid_file =  os.path.join(self.config['pid_dir'], 'ivicserver.pid')
            logger.debug("pid_file= %s,exe= %s",pid_file,ivicvpn_program+"/ivic-vnet-server")
            cf = ConfigParser.ConfigParser()
            if not os.path.exists(pid_file):
               os.system("touch " + pid_file)
            cf.read(pid_file)

            if "ivic-vnet-server" not in cf.sections():
                cf.add_section("ivic-vnet-server")
                cf.set("ivic-vnet-server","pid","0")
                cf.write(open(pid_file, "w"))
            res = cf.get("ivic-vnet-server","pid")
            pro = commands.getstatusoutput("ps -ef | awk '{print $2}' | grep -w " + res)
            if pro[0] != 0:
                pid = os.fork()
                if not pid:
                   close_all()
                   os.execvp(ivicvpn_program+"/ivic-vnet-server", [ivicvpn_program+"/ivic-vnet-server"])
                cf.set("ivic-vnet-server","pid",pid)
                cf.write(open(pid_file, "w"))

            if "switch" not in cf.sections():
                cf.add_section("switch")
                cf.set("switch","uuid",sid)
                cf.write(open(pid_file, "w"))
            else:
                switch = cf.get("switch","uuid")
                if switch == '':
                	cf.set("switch","uuid",sid)
                	cf.write(open(pid_file, "w"))
                else:
                	switch = switch.split(',')
                	for l in switch:
			    if sid == l:
				logger.warning('startVSwitch: switch %s already started, do nothing', sid)
				return

                	switch.append(sid)
                	switch=','.join(switch)

                	cf.set("switch","uuid",switch)
                	cf.write(open(pid_file, "w"))
                	logger.debug('ivicvpn server is started: set switch=%s',sid)
            
    @internalErrorHandler
    def stopVSwitch(self, sid):
        stype = self.db.vswitch_type(sid)
        logger.debug("stopVSwitch: uuid=%s, type=%s", sid, stype)

        if stype == 'openvpn':
            if not self.db.vswitch_started(sid):
                logger.warning('stopVSwitch: switch %s already stopped, do nothing', sid)
                return
        
            pid = self.db.get_pid(sid)
            try:
                logger.info('stopVSwitch: send SIGTERM to %d', pid)
                os.kill(pid, signal.SIGTERM)
            except Exception, e:
                logger.error('stopVSwitch: exception os.kill %s', e.message)
                #FIXME should return error state

        else:
		# if stype == 'ivic-vpn'
            tconfig = self.config['ivic-vpn']
	    ivicvpn_dir = tconfig['ivicserver_dir']
	    pid_file =  os.path.join(self.config['pid_dir'], 'ivicserver.pid')
            cf = ConfigParser.ConfigParser()
            if not os.path.exists(pid_file):
                logger.error("No ivic server pid file for vswitch")
            cf.read(pid_file)
            try:
                switch = cf.get("switch","uuid")
                switch = switch.split(',')
                if sid in switch :
                    switch.remove(sid)
                    switch = ','.join(switch)
                    cf.set("switch","uuid",switch)
                    cf.write(open(pid_file, "w"))
                    if switch == '':
                        pid = cf.get("ivic-vnet-server","pid")
                        try:
                            logger.info('no ivic-vpn VSwitch: kill server to %d', pid)
                            os.kill(int(pid), signal.SIGTERM)
                            cf.set("ivic-vnet-server","pid","0")
                            cf.write(open(pid_file,"w"))
                        except Exception, e:
                            logger.error('kill ivic-vpn server: exception os.kill %s', e.message)
                else :
                      logger.warning('stopVSwitch: switch %s already stopped, do nothing', sid)
            except:
                logger.error("No swtich entry for ivic server")
                return -1


    @internalErrorHandler
    def deregisterVSwitch(self, sid):
        stype = self.db.vswitch_type(sid)
        logger.debug("deregisterVSwitch: uuid=%s, type=%s", sid, stype)
       
        if stype == 'openvpn':
            if self.db.vswitch_started(sid):
                logger.error('deregisterVSwitch: switch %s started, unable to deregister ', sid)
                return

            tconfig = self.config['openvpn']
            sconf_dir = os.path.join(tconfig['server_dir'], sid)
            cconf_dir = os.path.join(tconfig['client_dir'], sid)
            try:
                logger.info('deregisterVSwitch: deleting tree %s', sconf_dir)
                shutil.rmtree(sconf_dir)
            except Exception, e:
                logger.error('deregisterVSwitch: error deleting tree %s', e.message)

            try:
                logger.info('deregisterVSwitch: deleting tree %s', cconf_dir)
                shutil.rmtree(cconf_dir)
            except Exception, e:
                logger.error('deregisterVSwitch: error deleting tree %s', e.message)
            
        else :
		# if stype == 'ivic-vpn':
            pid_file = os.path.join(self.config['pid_dir'], 'ivicserver.pid')
            cf = ConfigParser.ConfigParser()
            if not os.path.exists(pid_file):
                logger.error("No ivic server pid file for vswitch")
            cf.read(pid_file)
            try:
                switch = cf.get("switch","uuid")
                switch = switch.split(',')
                if sid in switch :
                    logger.error('deregisterVSwitch error: %s switch is not stop', sid)
                    return -1
            except:
                logger.error("No swtich entry for ivic server")
                return -1
            
            tconfig = self.config['ivic-vpn']
            ivicconf_dir = os.path.join(tconfig['config_dir'], sid)

            try:
                logger.info('deregisterVSwitch: deleting tree %s', ivicconf_dir)
                shutil.rmtree(ivicconf_dir)
            except Exception, e:
                logger.error('deregisterVSwitch: error deleting tree %s', e.message)
        self.db.deregister(sid)
